package cc.rengu.redp.flowable.service.impl;


import cc.rengu.redp.common.domain.QueryRequest;
import cc.rengu.redp.common.exception.RedpException;
import cc.rengu.redp.common.utils.DateUtil;
import cc.rengu.redp.flowable.bizrule.rule.BusiRuleCommander;
import cc.rengu.redp.flowable.bizrule.rule.BusiRulePrefix;
import cc.rengu.redp.flowable.bizrule.rule.RuleResult;
import cc.rengu.redp.flowable.domain.*;
import cc.rengu.redp.flowable.service.FlowDataService;
import cc.rengu.redp.flowable.service.ProcessService;
import cc.rengu.redp.flowable.service.SysWorkFlowService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.io.ByteStreams;
import com.googlecode.aviator.AviatorEvaluator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.task.Comment;
import org.flowable.idm.api.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static cc.rengu.redp.common.utils.DateUtil.FULL_TIME_SPLIT_PATTERN;

@Slf4j
@Service("processService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class ProcessServiceImpl implements ProcessService {

    @Autowired
    private SysWorkFlowService sysWorkFlowService;
    @Autowired
    private FlowDataService flowDataService;

    @Override
    public List<ApplyFormEntrance> getAvailableProcess(String userId) {
        List<ProcessDefinition> li = sysWorkFlowService.getAvailableProcesses(userId);
        List<ApplyFormEntrance> applyFormEntrances = new ArrayList<>();
        Iterator<ProcessDefinition> it = li.iterator();
        while (it.hasNext()) {
            ProcessDefinition processDefinition = it.next();
            String jsonStr = processDefinition.getDescription();
            if (StringUtils.isBlank(jsonStr)) {
                continue;
            }
            CustomProcess customProcess = JSON.parseObject(processDefinition.getDescription(), CustomProcess.class);
            //仅输出设置为"申请表单"的流程使用类型
            if (CustomProcess.PROCESS_USAGE_TYPE_APPLY_FORM.equals(customProcess.getProcessUsageType())) {
                CustomStartNode customStartNode = sysWorkFlowService.getStartCustomNodeByProcDefId(processDefinition.getId());
                ApplyFormEntrance applyFormEntrance = new ApplyFormEntrance();
                applyFormEntrance.setFormId(customStartNode.getFormId());
                applyFormEntrance.setTitle(customStartNode.getFormTitle());
                applyFormEntrance.setFormIcon(customStartNode.getFormIcon());
                applyFormEntrance.setProcessDefId(processDefinition.getKey());
                applyFormEntrances.add(applyFormEntrance);
            } else {
                it.remove();
            }
        }

        return applyFormEntrances;
    }

    @Override
    public Map<String, String> getAvailableProcess() {
        Map<String, String> availableProcess = new HashMap<>();
        List<ProcessDefinition> pds = sysWorkFlowService.getAvailableProcesses();
        pds.forEach(pd -> {
            CustomProcess customProcess = JSON.parseObject(pd.getDescription(), CustomProcess.class);
            if (CustomProcess.PROCESS_USAGE_TYPE_APPLICATION.equals(customProcess.getProcessUsageType())) {
                availableProcess.put(pd.getKey(), pd.getName());
            }
        });

        return availableProcess;
    }

    @Override
    public Map<String, Object> getWaitingToDo(String userId, String queryText, QueryRequest queryRequest) {
        Map<String, Object> result = new HashMap<>();
        if (StringUtils.isBlank(userId)) {
            result.put("rows", null);
            result.put("total", 0);
            return result;
        }
        List<WaitingToDo> waitingToDos = sysWorkFlowService.getWaitingToDo(userId, queryText, queryRequest.getPageNum(), queryRequest.getPageSize());
        waitingToDos.forEach(waitingToDo -> {
            List<String> applicants = new ArrayList<>();
            if (waitingToDo.getCurrNode().getUserIds() != null) {
                applicants.addAll(waitingToDo.getCurrNode().getUserIds());
            }
            if (waitingToDo.getCurrNode().getRoleNames() != null) {
                applicants.addAll(waitingToDo.getCurrNode().getRoleNames());
            }
            waitingToDo.setCurrApplicant(applicants.stream().collect(Collectors.joining("，")));

            List<Comment> comment = sysWorkFlowService.getCommentByProcInstId(waitingToDo.getProcInstId());
            if (comment.size() > 0) {
                waitingToDo.setComment(comment.get(0).getFullMessage());
            }
        });
        result.put("rows", waitingToDos);
        result.put("total", sysWorkFlowService.getWaitingToDoTotal(userId, queryText));
        return result;
    }

    @Override
    public Map<String, Object> getFlowData(String procInstId) {
        Map<String, Object> result = new HashMap<>();
        result.put("rows", flowDataService.getFlowData(procInstId));
        result.put("total", "1");
        return result;
    }

    @Override
    public Map<String, Object> getNextToDo(String taskId) {
        Map<String, Object> result = new HashMap<>();
        final JSONObject[] flowDataJson = {null};

        List<NextToDo> nextToDos = sysWorkFlowService.getOutComeListByTaskId(taskId, (procInstId, aviatorExpression) -> {
            /**
             * aviator分支显示规则判断的回调方法
             */
            if (flowDataJson[0] == null) {  //防止多次调用数据库
                FlowData flowData = flowDataService.getFlowData(procInstId);
                if (flowData == null) return false;  //正常情况下不大可能找不到数据
                flowDataJson[0] = JSONObject.parseObject(flowData.getFlowData());
            }

            String varArray[] = StringUtils.substringsBetween(aviatorExpression, "{", "}");
            Map<String, Object> envMap = new HashMap<>();
            String expression = RegExUtils.removeAll(aviatorExpression, "\\{|\\}");
            for (String var : varArray) {
                String value = flowDataJson[0].getString(var);
                if (StringUtils.isNotBlank(value)) {
                    envMap.put(var, value);
                }
            }

            try {
                return (Boolean) AviatorEvaluator.execute(expression, envMap);
            } catch (Exception e) {
                log.error("AviatorEvaluator execute error", e);
                return false;
            }
        });
        result.put("rows", nextToDos);
        result.put("total", nextToDos.size());
        return result;
    }

    @Override
    public void launch(String bizDataJson, String processDefinitionKey, String applicant, String nextAssignee) throws RedpException {
        ProcessDefinition pd = sysWorkFlowService.getProcessDefinitionByProcDefKey(processDefinitionKey);
        CustomStartNode customStartNode = sysWorkFlowService.getStartCustomNodeByProcDefId(pd.getId());

        RuleResult ruleResult;
        if (customStartNode != null) {  //启动为表单类型时，customStartNode不为空
            // 表单验证
            ruleResult = BusiRuleCommander.executeRuleList(
                    customStartNode.getSummitRule().getFsRules(),
                    JSON.parseObject(bizDataJson),
                    BusiRulePrefix.FSRule);
            if (RuleResult.FAILURE == ruleResult.getCode()) {
                throw new RedpException(ruleResult.getMessage());
            }

            // 准入验证
            ruleResult = BusiRuleCommander.executeRuleList(
                    customStartNode.getAccessRule().getAccRules(),
                    JSON.parseObject(bizDataJson),
                    BusiRulePrefix.ACCRule);
            if (RuleResult.FAILURE == ruleResult.getCode()) {
                throw new RedpException(ruleResult.getMessage());
            }
        }

        // 保存关联业务数据
        FlowData flowData = new FlowData();
        flowData.setFlowData(bizDataJson);
        flowData.setApplicant(applicant);
        flowData.setCreateTime(DateUtil.getDateFormat(new Date(), FULL_TIME_SPLIT_PATTERN));
        String businessKey = flowDataService.saveFlowData(flowData).toString();

        // 启动流程
        BizWorkFlowVar bizWorkFlowVar = new BizWorkFlowVar();
        if (StringUtils.isNotBlank(nextAssignee)) {
            bizWorkFlowVar.setAssignee(nextAssignee);
        }
        bizWorkFlowVar.setFlowDataId(businessKey);
        String procInstId = sysWorkFlowService.startProcess(processDefinitionKey, applicant, businessKey, bizWorkFlowVar);

        // 回填流程实例ID
        flowDataService.fixProcInstId(flowData.getId(), procInstId);
    }

    @Override
    public void completeTask(String taskId,
                             String flowStatus,
                             String procInstId,
                             String comment,
                             String flowData,
                             String applicant,
                             String nextAssignee) throws RedpException {
        JSONObject flowDataJson = JSON.parseObject(flowData);

        // 表单校验
        CustomUserNode customUserNode = sysWorkFlowService.getCustomNodeByTaskId(taskId);
        RuleResult ruleResult = BusiRuleCommander.executeRuleList(
                customUserNode.getSummitRule().getFsRules(),
                flowDataJson,
                BusiRulePrefix.FSRule);
        if (RuleResult.FAILURE == ruleResult.getCode()) {
            throw new RedpException(ruleResult.getMessage());
        }

        // 分支校验
        CustomSequenceFlow customSequenceFlow = sysWorkFlowService.getCustomSequenceFlowByTaskIdAndSeqId(taskId, flowStatus);
        if (customSequenceFlow != null) {
            if (CustomSequenceFlow.SelectRule.TYPE_PLUGINS.equals(customSequenceFlow.getSelectRule().getType())) {
                //插件程序校验
                ruleResult = BusiRuleCommander.executeRuleList(
                        customSequenceFlow.getSelectRule().getCtRules(),
                        flowDataJson,
                        BusiRulePrefix.CTRule);
                if (RuleResult.FAILURE == ruleResult.getCode()) {
                    throw new RedpException(ruleResult.getMessage());
                }
            } else if (CustomSequenceFlow.SelectRule.TYPE_PLUGINS.equals(customSequenceFlow.getSelectRule().getType())) {
                //aviator 表达式
                String aviatorExpression = customSequenceFlow.getSelectRule().getAviatorExpression();
                if (StringUtils.isNotBlank(aviatorExpression)) {
                    String varArray[] = StringUtils.substringsBetween(customSequenceFlow.getSelectRule().getAviatorExpression(), "{", "}");
                    Map<String, Object> envMap = new HashMap<>();
                    String expression = RegExUtils.removeAll(aviatorExpression, "\\{|\\}");
                    for (String var : varArray) {
                        String value = flowDataJson.getString(var);
                        if (StringUtils.isNotBlank(value)) {
                            envMap.put(var, value);
                        }
                    }

                    try {
                        if (!(Boolean) AviatorEvaluator.execute(expression, envMap)) {
                            throw new RedpException("根据流程定义，表单填写不合乎规则。");
                        }
                    } catch (Exception e) {
                        //出现异常可能是因为表达式填写不正确
                        log.error("AviatorEvaluator execute error", e);
                        throw new RedpException("本节点提交的表单限制规则不正确：" + e.getMessage());
                    }
                }
            }
        }

        // 添加业务数据
        FlowData busi = new FlowData();
        busi.setFlowData(flowData);
        busi.setProcInstId(procInstId);
        flowDataService.saveFlowData(busi);
        // 添加批注
        sysWorkFlowService.setComment(procInstId, taskId, applicant, comment);
        // 完成分支
        BizWorkFlowVar bizWorkflowVar = new BizWorkFlowVar();
        bizWorkflowVar.setFlowStatus(flowStatus);
        if (StringUtils.isNotBlank(nextAssignee)) {
            bizWorkflowVar.setAssignee(nextAssignee);
        }
        sysWorkFlowService.completeTaskByTaskId(taskId, bizWorkflowVar, applicant);
    }

    @Override
    public void completeReceiveTask(String procInstId, JSONObject newFlowData) {
    }

    @Override
    public Map<String, Object> getHisActivities(String procInstId) {
        Map<String, Object> result = new HashMap<>();
        List<HisActivity> hisActivities = sysWorkFlowService.getHisActByProcInstId(procInstId);
        result.put("rows", hisActivities);
        result.put("total", hisActivities.size());
        return result;
    }

    @Override
    public Map<String, Object> getAllProcInstances(String procDefName, String applicant, String procStatus, QueryRequest queryRequest) {
        Map<String, Object> result = new HashMap<>();
        List<ProcessManager> processManagers = sysWorkFlowService.getAllProcessInstance(procDefName, applicant, procStatus,
                queryRequest.getPageNum(), queryRequest.getPageSize());
        result.put("rows", processManagers);
        result.put("total", sysWorkFlowService.getAllProcessInstanceCount(procDefName, applicant, procStatus));
        return result;
    }

    @Override
    public ByteStreams getProcGraphic(String procDefId) {
        return null;
    }

    @Override
    public void terminateProcess(String procInstId) {
        sysWorkFlowService.terminateProcInst(procInstId);
    }

    @Override
    public Map<String, Object> getCandidateUsersPrevious(String sequenceId, String taskId) {
        CustomNodeVo customNodeVo = sysWorkFlowService.getTargetUserTaskCustomNode(sequenceId, taskId);
        List<String> userList = new ArrayList<>();
        boolean ifShowSetCandidateUser = customNodeVo.isSetCandidateUserPrevious();
        if (customNodeVo.isSetCandidateUserPrevious() && customNodeVo.isSetHistUserFirst()) {
            // 如果下一节点配置了"历史经办人优先"，则查找是否存在历史经办人：
            // -如果有，则无需让本节点选择下一经办人，由监听器自动分配；
            // -如果无，则由customNode.isSetCandidateUserPrevious属性决定
            String procInstId = sysWorkFlowService.getProcInstByTaskId(taskId);
            String hisAssignee = sysWorkFlowService.getHistAssigneeInThisProcInstByNodeId(procInstId, customNodeVo.getNodeId());
            if (StringUtils.isNotBlank(hisAssignee)) {
                ifShowSetCandidateUser = false;
            }
        }

        //获取可选用户列表
        if (ifShowSetCandidateUser) {
            if (customNodeVo.getUserIds() != null) {
                userList.addAll(customNodeVo.getUserIds());
            }
            if (customNodeVo.getRoleNames() != null) {
                List<User> userObjList = sysWorkFlowService.getAllByRoles(customNodeVo.getRoleNames());
                userObjList.forEach(user -> userList.add(user.getId()));
            }
        }

        Map<String, Object> map = new HashMap<>();
        map.put("userList", userList);
        map.put("ifShowSetCandidateUser", ifShowSetCandidateUser);
        return map;
    }

    @Override
    public Map<String, Object> getCandidateUsersPrevious(String procDefKey) {
        CustomNodeVo customNodeVo = sysWorkFlowService.getTargetUserTaskCustomNode(procDefKey);
        List<String> userList = new ArrayList<>();
        // 流程起始状态无需进行isSetHistUserFirst判断，下一节点必定不存在历史经办人
        if (customNodeVo.isSetCandidateUserPrevious()) {
            if (customNodeVo.getUserIds() != null) {
                userList.addAll(customNodeVo.getUserIds());
            }
            if (customNodeVo.getRoleNames() != null) {
                List<User> userObjList = sysWorkFlowService.getAllByRoles(customNodeVo.getRoleNames());
                userObjList.forEach(user -> userList.add(user.getId()));
            }
        }

        Map<String, Object> map = new HashMap<>();
        map.put("userList", userList);
        map.put("ifShowSetCandidateUser", customNodeVo.isSetCandidateUserPrevious());
        return map;
    }

    @Override
    public Map<String, Object> getInTransitProcess(String userId, String queryText, QueryRequest queryRequest) {
        Map<String, Object> result = new HashMap<>();
        if (StringUtils.isBlank(userId)) {
            result.put("rows", null);
            result.put("total", 0);
            return result;
        }
        List<WaitingToDo> waitingToDos = sysWorkFlowService.getInTransitProcess(userId, queryText, queryRequest.getPageNum(), queryRequest.getPageSize());
        List<WaitingToDo> lists = new ArrayList<>();
        waitingToDos.forEach(waitingToDo -> {
            List<String> applicants = new ArrayList<>();
            if (waitingToDo.getCurrNode().getUserIds() != null) {
                applicants.addAll(waitingToDo.getCurrNode().getUserIds());
            }
            if (waitingToDo.getCurrNode().getRoleNames() != null) {
                applicants.addAll(waitingToDo.getCurrNode().getRoleNames());
            }
            waitingToDo.setCurrApplicant(applicants.stream().collect(Collectors.joining("，")));

            List<Comment> comment = sysWorkFlowService.getCommentByProcInstId(waitingToDo.getProcInstId());
            if (comment.size() > 0) {
                waitingToDo.setComment(comment.get(0).getFullMessage());
            }
            if (waitingToDo.getApplicant().equals(userId)) {
                lists.add(waitingToDo);
            }
        });
        result.put("rows", lists);
        result.put("total", sysWorkFlowService.getInTransitProcessCount(userId, queryText));
        return result;
    }

    @Override
    public Map<String, Object> getLatelyClosedProcess(String userId, String queryText, QueryRequest queryRequest) {
        Map<String, Object> result = new HashMap<>();
        if (StringUtils.isBlank(userId)) {
            result.put("rows", null);
            result.put("total", 0);
            return result;
        }
        List<FinishedProcess> finishedProcessLists = sysWorkFlowService.getLatelyClosedProcess(
                userId, queryText, queryRequest.getPageNum(), queryRequest.getPageSize());
        result.put("rows", finishedProcessLists);
        result.put("total", sysWorkFlowService.getLatelyClosedProcessCount(userId, queryText));
        return result;
    }
}
